/**
 * 
 */
package gui.controller;

import exceptions.ValidRangeFormatException;
import gui.view.TrustPanel;
import gui.view.TrustSettingsTableModel;
import interfaces.IHypothesis;
import interfaces.IRange;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.swing.JOptionPane;

import massDistribution.ClassificationAttribute;
import massDistribution.ContinueMeasure;
import massDistribution.ContinueRange;
import massDistribution.DiscreteMeasure;
import massDistribution.DiscreteRange;
import massDistribution.MetricType;
import stubs.RangeParser;
import utilities.DoubleUtility;
import utilities.TrustEngineUtility;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

import core.FrameOfDiscernment;
import core.Hypothesis;

/**
 * @author Elisa Costante
 * 
 */
public class TrustPanelListener implements ActionListener {

	private TrustPanel panel;
	private ArrayList<ClassificationAttribute> classificationAttributesList = new ArrayList<ClassificationAttribute>();
	private ArrayList<IHypothesis> hyphotesesList = new ArrayList<IHypothesis>();

	public TrustPanelListener(TrustPanel panel) {
		this.panel = panel;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
	 */
	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getActionCommand().equalsIgnoreCase("addattributeButton")) {
			addattributeButtonAction();

		} else if (e.getActionCommand().equalsIgnoreCase("addTrustClassClass")) {
			addTrustClassClassAction();
		} else if (e.getActionCommand().equalsIgnoreCase("removeRow")) {
			removeRowAction();
		} else if (e.getActionCommand().equalsIgnoreCase("removeColumn")) {
			removeColumnAction();
		} else if (e.getActionCommand().equalsIgnoreCase("validate")) {
			if (validateAction())
				JOptionPane.showMessageDialog(panel, "The Framework is valid!");

		} else if (e.getActionCommand().equalsIgnoreCase("save")) {
			if (validateAction()) {
				saveAction();
				JOptionPane
						.showMessageDialog(panel, "File saved with success!");
			}

		} else if (e.getActionCommand().equalsIgnoreCase("load")) {
			loadAction();
		}

	}

	private void saveAction() {
		File fileChoose = CustomizedFileChooser.getFileToSave(panel);
		if (fileChoose != null) {
			saveServicesSettingsInXMLFile(fileChoose);
		} else {
			JOptionPane.showMessageDialog(panel,
					"Save action not accomplished correctly");
		}
	}

	/**
	 * @param fileChoose
	 */
	private void saveServicesSettingsInXMLFile(File fileChoose) {
		XStream xs = new XStream();

		// Write to a file in the file system
		try {
			FileOutputStream fs = new FileOutputStream(fileChoose);
			xs.toXML(classificationAttributesList, fs);

		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
		}

	}

	/**
	 * 
	 */
	private void loadAction() {
		File fileChoose = CustomizedFileChooser.getFileToOpen(panel);
		if (fileChoose != null) {
			loadServicesSettingsFromXMLFile(fileChoose);
		} else {
			JOptionPane.showMessageDialog(panel, "Load not accomplished");
		}

	}

	/**
	 * @param fileChoose
	 */
	@SuppressWarnings("unchecked")
	private void loadServicesSettingsFromXMLFile(File fileChoose) {
		XStream xs = new XStream(new DomDriver());
		classificationAttributesList = new ArrayList<ClassificationAttribute>();

		try {
			FileInputStream fis = new FileInputStream(fileChoose);
			classificationAttributesList = (ArrayList<ClassificationAttribute>) xs
					.fromXML(fis);
			panel.getFrame().setClassificationAttributesList(
					classificationAttributesList);
			showLoadModel();
		} catch (FileNotFoundException ex) {
			ex.printStackTrace();
		}

	}

	/**
	 * 
	 */
	private void showLoadModel() {
		TrustSettingsTableModel tableModel = new TrustSettingsTableModel();
		hyphotesesList = new ArrayList<IHypothesis>();
		// Find out the hypothesis
		ClassificationAttribute attribute = classificationAttributesList.get(0);
		// Set the column names
		if (attribute != null) {
			Enumeration<Hypothesis> allHyphotesis = attribute.getMap().keys();
			while (allHyphotesis.hasMoreElements()) {
				Hypothesis hyp = allHyphotesis.nextElement();
				hyphotesesList.add(hyp);
				String columName = hyp.getIdentifier();
				if (hyp.getValue() != null)
					columName = columName + "(" + hyp.getValue() + ")";
				tableModel.addColumn(columName);

			}
		}
		// Set the Frame Of DIscernment
		panel.getFrame().setFrameOfDiscernment(
				new FrameOfDiscernment(hyphotesesList));

		// set the rows
		tableModel.setRowCount(classificationAttributesList.size());
		int i = 0;
		for (ClassificationAttribute classificationAttribute : classificationAttributesList) {
			tableModel.setValueAt(classificationAttribute.getWeight()
					.toString(), i, TrustSettingsTableModel.WEIGHT_COLUMN);
			tableModel.setValueAt(classificationAttribute.getIdentifier(), i,
					TrustSettingsTableModel.ATTRIBUTE_NAME_COLUMN);
			tableModel.setValueAt(classificationAttribute.getMetric()
					.toString(), i,
					TrustSettingsTableModel.ATTRIBUTE_DETAILS_COLUMN);

			// Set the Trust classes
			int column = 3;
			for (IHypothesis hypothesis : hyphotesesList) {
				ArrayList<IRange> ranges = classificationAttribute
						.getRanges(hypothesis);
				String rangeToString = "";
				for (int index = 0; index < ranges.size(); index++) {
					rangeToString = rangeToString
							+ ranges.get(index).toString();
					// Check for not adding the semi-culomn to the last range
					if (index < ranges.size() - 1)
						rangeToString = rangeToString + ";";
				}
				ranges.toString();
				// rangeToString = rangeToString.replaceAll("\\[", "");
				// rangeToString = rangeToString.replaceAll("\\]", "");
				tableModel.setValueAt(rangeToString, i, column);
				column++;
			}

			i++;
		}
		panel.setTableModel(tableModel);
	}

	/**
	 * Validate the framework.
	 * 
	 * @return true if the frame is valid, false otherwise. If the framework is
	 *         not valid also show an Error message.
	 */
	private boolean validateAction() {
		// Validate the summation of weights
		TrustSettingsTableModel tableModel = panel.getTableModel();
		int rows = tableModel.getRowCount();
		int columns = tableModel.getColumnCount();

		if (weightsAreValid(rows, tableModel)) {
			// If weights are valid set up the attribute of each row
			boolean allValidAttributes = validateAllClassificationAttributes(
					tableModel, rows, columns);
			if (!allValidAttributes) {
				JOptionPane
						.showMessageDialog(
								panel,
								"Range's values must be consistent with the attribte's metric!",
								"Warning", JOptionPane.ERROR_MESSAGE);
				return false;

			} else {
				boolean allValidRanges = rangesAreValid();
				if (allValidRanges) {
					// If also the ranges are valid the framework is valid too
					panel.getFrame().setClassificationAttributesList(
							classificationAttributesList);
					return true;

				} else {
					JOptionPane.showMessageDialog(panel,
							"Not all the ranges are in a valid format.");
					return false;

				}

			}
		} else {
			// The summation of weights is major or minor than 1.
			JOptionPane
					.showMessageDialog(
							panel,
							"Check that the summation of all weights is equal to 1 and that weights are numbers and not characters.",
							"Warning", JOptionPane.ERROR_MESSAGE);
			return false;
		}

	}

	/**
	 * Validate the {@link ClassificationAttribute} for each row. Returns true
	 * if all {@link ClassificationAttribute} are valid, false otherwise.
	 * 
	 * @param tableModel
	 *            : data
	 * @param rows
	 *            : number of rows
	 * @param columns
	 *            : number of columns
	 * @return true if all {@link ClassificationAttribute} are valid, false
	 *         otherwise.
	 */
	private boolean validateAllClassificationAttributes(
			TrustSettingsTableModel tableModel, int rows, int columns) {
		boolean allValidAttributes = true;
		for (int i = 0; i < rows; i++) {
			allValidAttributes = setClassificationAttributeAtRow(i, columns,
					tableModel);
			if (!allValidAttributes) {

				return allValidAttributes;
			}
		}
		return allValidAttributes;
	}

	/**
	 * Set the {@link ClassificationAttribute} for the <code>i</code> row.
	 * 
	 * @param i
	 *            : the row
	 * @param columns
	 *            : the numebr of Trust Classes
	 * @param tableModel
	 *            : the data
	 * @return true if the row rapresent a valid {@link ClassificationAttribute}
	 *         , false otherwise. <code>classificationAttribute</code>, false
	 *         otherwise.
	 */
	private boolean setClassificationAttributeAtRow(int i, int columns,
			TrustSettingsTableModel tableModel) {

		int index;
		String attributeNAme = (String) tableModel.getValueAt(i,
				TrustSettingsTableModel.ATTRIBUTE_NAME_COLUMN);

		index = classificationAttributesList
				.indexOf(new ClassificationAttribute(attributeNAme));
		if (index >= 0) {
			Hashtable<Hypothesis, ArrayList<IRange>> trustMap = null;
			ClassificationAttribute classificationAttribute = classificationAttributesList
					.get(index);
			// Set the weight of the attribute
			setAttributeWeight(tableModel, i, classificationAttribute);
			boolean validRange = true;
			trustMap = new Hashtable<Hypothesis, ArrayList<IRange>>();

			for (int j = TrustSettingsTableModel.ATTRIBUTE_DETAILS_COLUMN + 1; j < columns; j++) {

				// Set the Hypothesis
				String hypothesisName = getHypothesisName(j, tableModel);
				Hypothesis hyp = new Hypothesis(hypothesisName);
				// Set The hyphotesis
				Double hypothesisValue = getHypothesisValue(j, tableModel);
				hyp.setValue(hypothesisValue);

				// Set the ClassificationAttribute
				String rangeString = (String) tableModel.getValueAt(i, j);

				ArrayList<IRange> rangesForTheHypothesis = setTrustClasses(
						rangeString, trustMap, classificationAttribute);
				if (rangesForTheHypothesis != null) {

					trustMap.put(hyp, rangesForTheHypothesis);

				} else {
					validRange = false;
				}
				if (!validRange)
					return false;
			}
			if (trustMap != null) {

				classificationAttribute.setMap(trustMap);
				return true;
			} else {
				return false;
			}
		} else {
			// The attribute cannot be found into the list
			return false;
		}
	}

	/**
	 * Read the ranges belong from the {@link ClassificationAttribute} for the
	 * specified {@link Hypothesis}.
	 * 
	 * @param tableModel
	 *            : the data model of the table.
	 * @param i
	 *            : the row of the <code>classificationAttribute</code>
	 * @param j
	 *            : the index of the trustClass
	 * @param trustMap
	 *            : the trustMAp to modify
	 * @param classificationAttribute
	 *            : the specified <code>classificationAttribute</code>.
	 * @return tue if ranges are valid, false otherwise.
	 * 
	 */
	private ArrayList<IRange> setTrustClasses(String rangeString,
			Hashtable<Hypothesis, ArrayList<IRange>> trustMap,
			ClassificationAttribute classificationAttribute) {

		ArrayList<IRange> ranges = null;

		if (rangeString == null || rangeString.equalsIgnoreCase("")) {
			JOptionPane
					.showMessageDialog(panel,
							"The empty range is not valid. If you mean NOT_MEASURED, please type it.");
			return ranges;
		} else {
			// The range is not empty
			if (rangeString.equalsIgnoreCase("not_measured")) {
				ranges = setNotMeasuredRangeList(classificationAttribute);

			} else {
				try {
					ranges = RangeParser.parseRange(rangeString,
							classificationAttribute.getMetric().getType());

				} catch (ValidRangeFormatException e) {
					JOptionPane.showMessageDialog(panel, e.getMessage());
					e.printStackTrace();
				}

			}
			return ranges;

		}
	}

	/**
	 * Set the range with not_measured value.
	 * 
	 * @param classificationAttribute
	 * @return
	 */
	private ArrayList<IRange> setNotMeasuredRangeList(
			ClassificationAttribute classificationAttribute) {
		ArrayList<IRange> ranges = new ArrayList<IRange>();

		if (classificationAttribute.getMetric().getType().getName()
				.equalsIgnoreCase(MetricType.INTEGER.getName())
				|| classificationAttribute.getMetric().getType().getName()
						.equalsIgnoreCase(MetricType.REAL.getName())) {
			// Case with Continuous Attribute
			ContinueRange continueRange = new ContinueRange(
					ContinueMeasure.NOT_MEASURED, ContinueMeasure.NOT_MEASURED,
					false, false);
			ranges.add(continueRange);

		} else if (classificationAttribute.getMetric().getType().getName()
				.equalsIgnoreCase(MetricType.DISCRETE.getName())) {
			// Case with Discrete Attribute
			DiscreteRange discreteRange = new DiscreteRange();
			discreteRange.addElement(DiscreteMeasure.NOT_MEASURED);
			ranges.add(discreteRange);

		}
		return ranges;
	}

	/**
	 * Read the attribute's weight from the table and if it is a valid number
	 * then set it for the attribute otherwise show an error message.
	 * 
	 * @param tableModel
	 * @param i
	 * @param attr
	 */
	private void setAttributeWeight(TrustSettingsTableModel tableModel, int i,
			ClassificationAttribute attr) {
		// Set the weight
		String weightString = (String) tableModel.getValueAt(i,
				TrustSettingsTableModel.WEIGHT_COLUMN);
		if (TrustEngineUtility.isANumber(weightString)) {
			// Set the weight
			attr.setWeight(Double.parseDouble(weightString));
		} else {
			JOptionPane.showMessageDialog(panel, "The weight must be a number");
		}
	}

	/**
	 * @param column
	 * @param tableModel
	 * @return
	 */
	private Double getHypothesisValue(int column,
			TrustSettingsTableModel tableModel) {
		String trustClassName = tableModel.getColumnName(column);
		int start = trustClassName.indexOf("(");
		int finish = trustClassName.indexOf(")");
		if (start >= 0 && finish >= 0) {
			String value = trustClassName.substring(start + 1, finish);
			return Double.parseDouble(value);
		} else
			return null;
	}

	/**
	 * @return true if and only if the range inserted from the user are valid
	 *         respect to the metric, false otherwise.
	 */
	private boolean rangesAreValid() {
		if (classificationAttributesList != null) {
			for (ClassificationAttribute classificationAttribute : classificationAttributesList) {
				ArrayList<IRange> validRanges = classificationAttribute
						.getMetric().getValidRanges();
				for (IRange validRange : validRanges) {
					for (IHypothesis hypothesis : hyphotesesList) {
						ArrayList<IRange> ranges = classificationAttribute
								.getRanges(hypothesis);
						if (ranges != null) {
							for (IRange range : ranges) {
								if (!validRange.include(range)) {
									System.out
											.println("the range not valid is the following: "
													+ range.toString());
									return false;
								}

							}
						}
					}
				}
			}
			return true;
		}
		return false;
	}

	/**
	 * @param rows
	 *            : the number of rows of the table model
	 * @param tableModel
	 *            : the <code>table model</code> contains the data.
	 * @return true if the summation of all the weiths is equal to 1, false
	 *         otherwise.
	 */
	private boolean weightsAreValid(int rows, TrustSettingsTableModel tableModel) {
		double sum = 0;
		for (int i = 0; i < rows; i++) {
			String weightString = (String) tableModel.getValueAt(i,
					TrustSettingsTableModel.WEIGHT_COLUMN);
			if (weightString != null
					&& TrustEngineUtility.isANumber(weightString)) {
				double weight = Double.parseDouble(weightString);
				sum = sum + weight;
			} else
				return false;
		}
		return (DoubleUtility.areEqualsDouble(sum, 1.0, DoubleUtility.EPSILON));
	}

	/**
	 * 
	 */
	private void removeColumnAction() {
		int selectedColumn = panel.getConfigurationDbTable()
				.getSelectedColumn();
		if (selectedColumn >= 0) {
			if (selectedColumn != TrustSettingsTableModel.ATTRIBUTE_DETAILS_COLUMN
					&& selectedColumn != TrustSettingsTableModel.ATTRIBUTE_NAME_COLUMN
					&& selectedColumn != TrustSettingsTableModel.WEIGHT_COLUMN) {
				TrustSettingsTableModel tableModel = panel.getTableModel();

				// Remove also the Hypothesis related to the trustClass
				String hypothesisName = getHypothesisName(selectedColumn,
						tableModel);
				Hypothesis hyp = (Hypothesis) getHyphotesis(hypothesisName);
				if (hyphotesesList.contains(hyp)) {
					hyphotesesList.remove(hyp);
					// Remove the column also from the model
					tableModel.removeColumnAndData(panel
							.getConfigurationDbTable(), selectedColumn);
				} else {
					JOptionPane
							.showMessageDialog(panel,
									"Such Trust Class doesn't exist in the Trust Classes List.");
				}

			} else
				JOptionPane.showMessageDialog(panel,
						"You cannot remove this column");
		} else
			JOptionPane.showMessageDialog(panel,
					"You should select the column to remove");

	}

	/**
	 * @param selectedColumn
	 * @param tableModel
	 * @return
	 */
	private String getHypothesisName(int selectedColumn,
			TrustSettingsTableModel tableModel) {

		String trustClassName = tableModel.getColumnName(selectedColumn);
		int index = trustClassName.indexOf("(");
		if (index >= 0) {
			trustClassName = trustClassName.substring(0, index);

		}
		trustClassName = trustClassName.substring(0, index);

		return trustClassName;
	}

	/**
	 * Return the hyphotesis into the list with the <code>hypName</code> or null
	 * if such Hyphotesis doesn't exist.
	 * 
	 * @param hypName
	 * @return
	 */
	public IHypothesis getHyphotesis(String hypName) {
		for (IHypothesis hyp : hyphotesesList) {
			if (hyp.getIdentifier().equalsIgnoreCase(hypName))
				return hyp;
		}
		return null;
	}

	/**
	 * Remove a classification attribute from the list.
	 */
	private void removeRowAction() {

		int selectedRow = panel.getConfigurationDbTable().getSelectedRow();
		if (selectedRow >= 0) {
			TrustSettingsTableModel tableModel = panel.getTableModel();
			tableModel.removeRow(selectedRow);

			// remove the classification attribute form the list
			ClassificationAttribute cl = new ClassificationAttribute(
					(String) tableModel.getValueAt(selectedRow,
							TrustSettingsTableModel.ATTRIBUTE_NAME_COLUMN));
			if (classificationAttributesList.contains(cl))
				classificationAttributesList.remove(cl);
		} else {
			JOptionPane.showMessageDialog(panel,
					"You should select the row to remove");

		}
	}

	/**
	 * 
	 */
	private void addTrustClassClassAction() {
		TrustSettingsTableModel tableModel = panel.getTableModel();
		String className = panel.getTrustNameTxtField().getText();
		String trustValue = panel.getTrustValueTxtField().getText();
		if (!className.equalsIgnoreCase("") && className != null) {
			// Set the trust class as an hyphotesis
			Hypothesis hyp = new Hypothesis(className);
			if (!hyphotesesList.contains(hyp)) {
				hyphotesesList.add(hyp);
				panel.getFrame().setFrameOfDiscernment(
						new FrameOfDiscernment(hyphotesesList));

				// If the TrustClass is not empty add a new Class
				Double trustNumberValue = null;
				if (trustValue != null && !trustValue.equals("")) {
					// If the value of trust is not empty be sure that it is
					// a
					// number
					if (TrustEngineUtility.isANumber(trustValue)) {
						className = className + "(" + trustValue + ")";
						trustNumberValue = Double.parseDouble(trustValue);
						hyp.setValue(trustNumberValue);

					} else {
						JOptionPane.showMessageDialog(panel,
								"The Trust Value must be a number.", "Worning",
								JOptionPane.ERROR_MESSAGE);
					}
				}
				// Add a column to the model
				tableModel.addColumn(className);
			} else
				JOptionPane.showMessageDialog(panel,
						"The list Already contains such Hypothesis.",
						"Worning", JOptionPane.ERROR_MESSAGE);

		} else
			JOptionPane.showMessageDialog(panel,
					"You cannot add an empty Trust Class");

	}

	/**
	 * 
	 */
	private void addattributeButtonAction() {
		if (panel.getFrame().getAttributeList() != null
				&& panel.getFrame().getAttributeList().size() > 0) {
			TrustSettingsTableModel tableModel = panel.getTableModel();
			String attributeIdentifier = (String) panel.getAttributeCombo()
					.getSelectedItem();
			ClassificationAttribute classificationAttribute = new ClassificationAttribute(
					attributeIdentifier);

			if (!classificationAttributesList.contains(classificationAttribute)) {

				// Add the ClassificationAttribute to the list
				classificationAttributesList.add(classificationAttribute);
				classificationAttribute.setMetric(panel.getFrame()
						.getAttributeList().get(attributeIdentifier)
						.getMetric());
				// add a new row to the table
				tableModel.addRow(new Vector());
				int row = tableModel.getRowCount();
				tableModel.setValueAt(attributeIdentifier, row - 1,
						TrustSettingsTableModel.ATTRIBUTE_NAME_COLUMN);
				tableModel.setValueAt(panel.getFrame().getAttributeList().get(
						attributeIdentifier).getMetric().toString(), row - 1,
						TrustSettingsTableModel.ATTRIBUTE_DETAILS_COLUMN);
			} else
				JOptionPane.showMessageDialog(panel,
						"The table already contains this attribute");

		} else {
			JOptionPane.showMessageDialog(panel,
					"You should set the Attribute list before.");
		}
	}
}
